package fem2.observer;

import inf.jlinalg.IVector;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import fem2.Debugger;
import fem2.Edge;
import fem2.Element;
import fem2.Face;
import fem2.Mesh;
import fem2.MeshUtilities;
import fem2.Model;
import fem2.Node;
import fem2.Observer;
import fem2.element.EmbeddedCrackElement;
import fem2.pre_and_post.GidPost;
import fem2.strategies.NewtonRaphsonStrategy;
import fem2.strategies.Strategy;

public class CrackObserver implements Observer {

	private Model m;
	private Strategy s;

	private ArrayList<ArrayList<double[]>> crackPath;
	private Map<Element, Boolean> isCracked;
	private String gmshFileName = null;
	private GidPost gidPost = null;

	/**
	 * 
	 * @param m
	 * @param s
	 *            set to null if there is no need to export for post-processor
	 */
	public CrackObserver(Model m, Strategy s) {
		this.m = m;
		this.s = s;
		if (!(m.getElement(0) instanceof EmbeddedCrackElement)) {
			Debugger.error(this.getClass().getName()
					+ "only support for model contains crack element");
		}
	}

	/**
	 * set the observer to export mesh contains crack to Gmsh
	 * 
	 * @param gmshFileName
	 */
	public void setOutputTarget(String gmshFileName) {
		this.gmshFileName = gmshFileName;
		crackPath = new ArrayList<ArrayList<double[]>>();
		isCracked = new HashMap<Element, Boolean>();
	}

	/**
	 * set the observer to export crack results to Gid
	 * 
	 * @param projectGidDir
	 * @param projectGidName
	 */
	public void setOutputTarget(String projectGidDir, String projectGidName) {
		gidPost = new GidPost(projectGidDir, projectGidName);
	}

	/**
	 * set the observer to export crack results to Gid
	 * 
	 * @param projectGidDir
	 * @param projectGidName
	 * @param fileName
	 */
	public void setOutputTarget(String projectGidDir, String projectGidName, String fileName) {
		gidPost = new GidPost(projectGidDir, projectGidName, fileName);
	}

	@Override
	public void fireStarted() {
	}

	@Override
	public void fireTimeStepStarted() {
	}

	@Override
	public void fireIterationStarted() {
	}

	@Override
	public void fireIterationFinished() {
	}

	@Override
	public void fireTimeStepFinished() {
		int ne = m.countElements();

		/*
		 * gmsh section (default)
		 */
		if (gmshFileName != null) {
			for (int i = 0; i < ne; i++) {
				Element e = m.getElement(i);
				if (e instanceof EmbeddedCrackElement) {
					EmbeddedCrackElement ce = (EmbeddedCrackElement) e;
					if (ce.isCrack()) {
						if (isCracked.get(e) == null || isCracked.get(e) == false) {
							crackPath.add(ce.getX());
							isCracked.put(e, true);
						}
					}
				}
			}
		}

		/*
		 * gid section. enabled when gidpost!=null
		 */
		if (gidPost != null) {
			for (String elemType : m.getElementTypes()) {
				ArrayList<Integer> elemIds = new ArrayList<Integer>();
				ArrayList<Boolean> isCrackList = new ArrayList<Boolean>();
				ArrayList<IVector> xiList = new ArrayList<IVector>();
				int nxi = 0;
				for (int i = 0; i < ne; i++) {
					Element e = m.getElement(i);
					if (e.getElemType().equals(elemType)) {
						elemIds.add(e.getId());
						if (e instanceof EmbeddedCrackElement) {
							EmbeddedCrackElement ce = (EmbeddedCrackElement) e;
							if (ce.isCrack()) {
								int tmp = ce.getCrackOpeningParameter().getSize();
								if (tmp > nxi)
									nxi = tmp;
								xiList.add(ce.getCrackOpeningParameter());
								isCrackList.add(true);
							} else {
								xiList.add(null);
								isCrackList.add(false);
							}
						} else {
							xiList.add(null);
							isCrackList.add(false);
						}

					}
				}
				/*
				 * write block of values for isCrack
				 */
				String isCrackedVarName = "IS_CRACKED";
				String xiVarName = "CRACK_OPENING_PARAMETER";
				double timeStep = ((NewtonRaphsonStrategy) s).getCurrentTimeStep();
				int[] elementIds = new int[elemIds.size()];
				double[][] isCrackValues = new double[elemIds.size()][1];
				double[][][] xiValues = new double[nxi][elemIds.size()][1];
				for (int i = 0; i < elemIds.size(); i++) {
					elementIds[i] = elemIds.get(i);
					isCrackValues[i][0] = isCrackList.get(i) ? 1 : 0;
					if (xiList.get(i) == null) {
						// for (int j = 0; j < nxi; j++) {
						// xiValues[j][i][0] = 0;
						// }
					} else if (xiList.get(i).getSize() < nxi) {
						for (int j = 0; j < xiList.get(i).getSize(); j++) {
							xiValues[j][i][0] = 0;
						}
					} else {
						for (int j = 0; j < nxi; j++) {
							xiValues[j][i][0] = xiList.get(i).get(j);
						}
					}
				}
				try {
					String groupName = "is_cracked_gp_" + elemType;
					gidPost.writeGausspointScalarResult(isCrackedVarName, groupName, elemType,
							elementIds, isCrackValues, timeStep);
					for (int i = 0; i < nxi; i++) {
						groupName = "XI" + (i + 1) + "_gp_" + elemType;
						gidPost.writeGausspointScalarResult(xiVarName, groupName, elemType,
								elementIds, xiValues[i], timeStep);
					}
				} catch (IOException e) {
					throw new Error("Write failed: " + e.getMessage());
				}
			}
		}
	}

	@Override
	public void fireFinished() {
		/*
		 * if gmsh is set then export to gmsh mesh
		 */
		if (gmshFileName != null) {
			Mesh mesh = createMesh();
			try {
				MeshUtilities.writeGmshMesh(mesh, gmshFileName);
			} catch (IOException e) {
				Debugger.error("write Gmsh mesh file failed");
			}
		}
	}

	private Mesh createMesh() {
		Mesh mesh = m.getMesh();
		for (int i = 0; i < crackPath.size(); i++) {
			ArrayList<double[]> crackFace = crackPath.get(i);
			if (crackFace.size() == 2) {
				Node n1 = mesh.addNode(new Node(crackFace.get(0)));
				Node n2 = mesh.addNode(new Node(crackFace.get(1)));
				mesh.addEdge(new Edge(n1, n2));
			} else {
				int nn = crackFace.size();
				Node[] nodes = new Node[nn];
				for (int j = 0; j < nn; j++) {
					nodes[i] = mesh.addNode(new Node(crackFace.get(j)));
				}
				mesh.addFace(new Face(nodes));
			}
		}
		return mesh;
	}

}
